/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Marcus Britanicus (https://gitlab.com/marcusbritanicus)
 * Copyright (c) 2021 Abrar (https://gitlab.com/s96Abrar)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 **/

#include <QDebug>

#include "wayqt/WindowManager.hpp"

#include "wlr-foreign-toplevel-management-unstable-v1-client-protocol.h"

WQt::WindowManager::WindowManager( zwlr_foreign_toplevel_manager_v1 *tlMgr ) {
    mObj = tlMgr;
    zwlr_foreign_toplevel_manager_v1_add_listener( mObj, &mWindowMgrListener, this );
}


WQt::WindowManager::~WindowManager() {
    zwlr_foreign_toplevel_manager_v1_stop( mObj );
    zwlr_foreign_toplevel_manager_v1_destroy( mObj );
}


void WQt::WindowManager::setup() {
    if ( mIsSetup == false ) {
        mIsSetup = true;

        for ( WQt::WindowHandle *hndl: mTopLevels ) {
            emit newTopLevelHandle( hndl );
        }
    }
}


zwlr_foreign_toplevel_manager_v1 *WQt::WindowManager::get() {
    return mObj;
}


WQt::WindowHandles WQt::WindowManager::windowHandles() {
    return mTopLevels;
}


void WQt::WindowManager::handleTopLevelAdded( void *data, zwlr_foreign_toplevel_manager_v1 *, zwlr_foreign_toplevel_handle_v1 *hndl ) {
    WindowManager *winMgr = reinterpret_cast<WindowManager *>(data);

    WQt::WindowHandle *handle = new WQt::WindowHandle( hndl );

    winMgr->mTopLevels << handle;

    if ( winMgr->mIsSetup ) {
        emit winMgr->newTopLevelHandle( handle );
    }
}


void WQt::WindowManager::handleFinished( void *data, zwlr_foreign_toplevel_manager_v1 * ) {
    WindowManager *winMgr = reinterpret_cast<WindowManager *>(data);
    emit winMgr->finished();
}


const zwlr_foreign_toplevel_manager_v1_listener WQt::WindowManager::mWindowMgrListener = {
    handleTopLevelAdded,
    handleFinished,
};


/**
 * Window Handle Wrapper
 */

WQt::WindowHandle::WindowHandle( zwlr_foreign_toplevel_handle_v1 *hndl ) {
    if ( hndl ) {
        mObj = hndl;
        zwlr_foreign_toplevel_handle_v1_add_listener( mObj, &mWindowHandleListener, this );
    }
}


WQt::WindowHandle::~WindowHandle() {
    zwlr_foreign_toplevel_handle_v1_destroy( mObj );
}


void WQt::WindowHandle::setup() {
    if ( mObj && (mIsSetup == false) ) {
        mIsSetup = true;

        if ( mFirstPendingTitle ) {
            mFirstPendingTitle = false;
            emit titleChanged();
        }

        if ( mFirstPendingAppId ) {
            mFirstPendingAppId = false;
            emit appIdChanged();
        }

        if ( mFirstPendingState ) {
            mFirstPendingState = false;
            emit stateChanged();
        }

        if ( mFirstPendingOutput ) {
            mFirstPendingOutput = false;

            if ( mOutput != nullptr ) {
                emit outputEntered( mOutput );
            }
        }

        if ( mFirstPendingParent ) {
            mFirstPendingParent = false;
            emit parentChanged( mParent );
        }
    }
}


QString WQt::WindowHandle::appId() const {
    return mAppId;
}


QString WQt::WindowHandle::title() const {
    return mTitle;
}


/** ======== Activated ======== */
bool WQt::WindowHandle::isActivated() {
    return mViewState.activated;
}


void WQt::WindowHandle::activate( wl_seat *seat ) {
    zwlr_foreign_toplevel_handle_v1_activate( mObj, seat );
}


/** ======== Maximized ======== */
bool WQt::WindowHandle::isMaximized() {
    return mViewState.maximized;
}


void WQt::WindowHandle::setMaximized() {
    zwlr_foreign_toplevel_handle_v1_set_maximized( mObj );
}


void WQt::WindowHandle::unsetMaximized() {
    zwlr_foreign_toplevel_handle_v1_unset_maximized( mObj );
}


/** ======== Minimized ======== */
bool WQt::WindowHandle::isMinimized() {
    return mViewState.minimized;
}


void WQt::WindowHandle::setMinimized() {
    zwlr_foreign_toplevel_handle_v1_set_minimized( mObj );
}


void WQt::WindowHandle::unsetMinimized() {
    zwlr_foreign_toplevel_handle_v1_unset_minimized( mObj );
}


/** ======== FullScreen ======== */
bool WQt::WindowHandle::isFullScreen() {
    return mViewState.fullscreen;
}


void WQt::WindowHandle::setFullScreen( wl_output *op ) {
    zwlr_foreign_toplevel_handle_v1_set_fullscreen( mObj, op );
}


void WQt::WindowHandle::unsetFullScreen() {
    zwlr_foreign_toplevel_handle_v1_unset_fullscreen( mObj );
}


/** ======== Minimize Rect ======== */
void WQt::WindowHandle::setMinimizeRect( wl_surface *surf, QRect rect ) {
    zwlr_foreign_toplevel_handle_v1_set_rectangle( mObj, surf, rect.x(), rect.y(), rect.width(), rect.height() );
}


/** ======== Minimize Rect ======== */
void WQt::WindowHandle::close() {
    zwlr_foreign_toplevel_handle_v1_close( mObj );
}


zwlr_foreign_toplevel_handle_v1 *WQt::WindowHandle::get() {
    return mObj;
}


void WQt::WindowHandle::handleTitle( void *data, zwlr_foreign_toplevel_handle_v1 *, const char *title ) {
    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);

    handle->mTitle = title;

    if ( handle->mIsSetup ) {
        emit handle->titleChanged();
    }

    else {
        handle->mFirstPendingTitle = true;
    }
}


void WQt::WindowHandle::handleAppId( void *data, zwlr_foreign_toplevel_handle_v1 *, const char *app_id ) {
    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);

    handle->mAppId = app_id;

    if ( handle->mIsSetup ) {
        emit handle->appIdChanged();
    }

    else {
        handle->mFirstPendingAppId = true;
    }
}


void WQt::WindowHandle::handleOutputEnter( void *data, zwlr_foreign_toplevel_handle_v1 *, wl_output *wlOut ) {
    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);

    handle->mOutput = wlOut;

    if ( handle->mIsSetup ) {
        emit handle->outputEntered( wlOut );
    }

    else {
        handle->mFirstPendingOutput = true;
    }
}


void WQt::WindowHandle::handleOutputLeave( void *data, zwlr_foreign_toplevel_handle_v1 *, wl_output *wlOut ) {
    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);

    if ( handle->mOutput == wlOut ) {
        handle->mOutput = nullptr;
    }

    emit handle->outputLeft( wlOut );
}


void WQt::WindowHandle::handleState( void *data, zwlr_foreign_toplevel_handle_v1 *, wl_array *state ) {
    auto *states   = static_cast<uint32_t *>(state->data);
    int  numStates = static_cast<int>(state->size / sizeof(uint32_t) );

    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);

    for ( int i = 0; i < numStates; i++ ) {
        switch ( (uint32_t)states[ i ] ) {
            case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MAXIMIZED: {
                handle->mPendingState.maximized = true;
                break;
            }

            case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_MINIMIZED: {
                handle->mPendingState.minimized = true;
                break;
            }

            case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_ACTIVATED: {
                handle->mPendingState.activated = true;
                break;
            }

            case ZWLR_FOREIGN_TOPLEVEL_HANDLE_V1_STATE_FULLSCREEN: {
                handle->mPendingState.fullscreen = true;
                break;
            }
        }
    }
}


void WQt::WindowHandle::handleDone( void *data, zwlr_foreign_toplevel_handle_v1 * ) {
    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);

    /** Update the current view state */
    handle->mViewState = handle->mPendingState;

    /** Reset for next use */
    handle->mPendingState.maximized  = false;
    handle->mPendingState.minimized  = false;
    handle->mPendingState.activated  = false;
    handle->mPendingState.fullscreen = false;

    if ( handle->mIsSetup ) {
        emit handle->stateChanged();
    }

    else {
        handle->mFirstPendingState = true;
    }
}


void WQt::WindowHandle::handleClosed( void *data, zwlr_foreign_toplevel_handle_v1 * ) {
    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);
    emit handle->closed();
}


void WQt::WindowHandle::handleParent( void *data, zwlr_foreign_toplevel_handle_v1 *, zwlr_foreign_toplevel_handle_v1 *parent ) {
    WindowHandle *handle = reinterpret_cast<WindowHandle *>(data);

    handle->mParent = new WindowHandle( parent );

    if ( handle->mIsSetup ) {
        emit handle->parentChanged( new WindowHandle( parent ) );
    }

    else {
        handle->mFirstPendingParent = true;
    }
}


const zwlr_foreign_toplevel_handle_v1_listener WQt::WindowHandle::mWindowHandleListener = {
    handleTitle,
    handleAppId,
    handleOutputEnter,
    handleOutputLeave,
    handleState,
    handleDone,
    handleClosed,
    handleParent,
};
